package com.ynet.middleground.contract.model;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.dozer.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Throwables;
import com.timevale.esign.sdk.tech.bean.*;
import com.timevale.esign.sdk.tech.bean.result.CodeMultiSignResult;
import com.timevale.esign.sdk.tech.bean.result.FileDigestSignResult;
import com.timevale.esign.sdk.tech.bean.result.SignDataResult;
import com.timevale.esign.sdk.tech.impl.constants.SignType;
import com.ynet.core.common.BaseReqObj;
import com.ynet.core.exception.BusinessException;
import com.ynet.core.log.IfpLogger;
import com.ynet.file.service.FileService;
import com.ynet.host.HostConstants;
import com.ynet.middleground.contract.bean.*;
import com.ynet.middleground.contract.constant.SignServerEnum;
import com.ynet.middleground.contract.dto.*;
import com.ynet.middleground.contract.entity.Contract;
import com.ynet.middleground.contract.entity.ServiceRecord;
import com.ynet.middleground.contract.entity.SourceSignBean;
import com.ynet.middleground.contract.entity.UserSeal;
import com.ynet.middleground.contract.external.cfca.CfcaConfig;
import com.ynet.middleground.contract.external.esign.ApiHelper;
import com.ynet.middleground.contract.external.esign.ApiUri;
import com.ynet.middleground.contract.external.esign.SignHelper;
import com.ynet.middleground.contract.utils.BeanUtil;
import com.ynet.middleground.contract.utils.CommUtils;
import com.ynet.middleground.contract.utils.TemplateUtil;

import cfca.sadk.algorithm.common.Mechanism;
import cfca.sadk.algorithm.common.PKIException;
import cfca.sadk.util.Base64;
import cfca.seal.bean.CalculateHashInfo;
import cfca.seal.bean.SignLocation;
import cfca.seal.common.Constants;
import cfca.seal.sadk.PrePdfSeal;
import cfca.seal.servlet.SealClient;
import cfca.seal.util.StrategyUtil;
import cfca.seal.util.StringUtil;
import cfca.timestamp.client.bean.TimeStamp;
import cfca.timestamp.client.session.TsaSession;

/**
 * @author liulx
 * @description 签名签章业务处理类
 * @date 2020-04-11 20:42
 */
@Component
public class SignModel {

    @Autowired
    SignHelper signHelper;

    @Autowired
    Mapper mapper;

    @Autowired
    TsaSession tsaSession;

    @Autowired
    ServiceRecordModel serviceRecordModel;

    @Autowired
    ApiHelper apiHelper;

    @Autowired
    CfcaConfig cfcaConfig;

    @Autowired
    FileService fileService;

    @Autowired
    CommUtils commUtils;

    @Autowired
    SealModel sealModel;

    @Autowired
    ContractManagementModel contractManagementModel;

    /**
     * 发送签署短信验证码
     *
     * @param req 发送签署短信验证码类
     */
    public void sendSignMobileCode(SendSignMobileCodeReq req) {
        signHelper.sendSignMobileCode(req.getAccountId());
    }

    /**
     * 指定手机发送签署短信验证码
     *
     * @param req 指定手机发送签署短信验证码类
     */
    public void sendAppointSignMobileCode(SendAppointSignMobileCodeReq req) {
        signHelper.sendAppointSignMobileCode(req);
    }

    /**
     * 文本签加签
     *
     * @param req 文本签请求类
     * @return 文本签结果数据
     */
    public TextSignResultDTO textSign(TextSignReq req) {
        SignDataResult result = signHelper.textSign(req.getAccountId(), req.getSrcData(), req.getCode());
        ServiceRecord record = new ServiceRecord();
        record.setRequestMsg(req.getSrcData());
        record.setSceneDesc(req.getSceneDesc());
        record.setContractId(req.getContractId());
        if (0 != result.getErrCode()) {
            record.setServiceStatus(false);
            serviceRecordModel.insertServiceRecord(record, SignServerEnum.TEXT_SIGN, req);
            String exMsg = MessageFormat.format("{0}", result.getMsg());
            throw new BusinessException(exMsg, String.valueOf(result.getErrCode()));
        }
        record.setServiceStatus(true);
        record.setSignServiceId(result.getSignId());
        record.setResponseMsg(result.getSignResult());
        serviceRecordModel.insertServiceRecord(record, SignServerEnum.TEXT_SIGN, req);
        TextSignResultDTO signResult = new TextSignResultDTO();
        signResult.setSignResult(result.getSignResult());
        signResult.setSignId(result.getSignId());
        return signResult;
    }

    /**
     * 文本签验签
     *
     * @param req 文本签验签请求类
     * @return 文本签名证书信息
     */
    public EsignCertDTO verifyTextSign(VerifyTextSignReq req) {
        CertBean certBean = signHelper.verifyTextSignResult(req.getSrcData(), req.getSignResult());
        return mapper.map(certBean, EsignCertDTO.class);
    }

    /**
     * pdf 文档验签
     *
     * @param req pdf 文档验签请求信息
     * @return pdf 文档所有签名信息列表
     */
    public List<SignBeanDTO> verifyPdfSign(VerifyPdfSignReq req) throws IOException {
        File file = TemplateUtil.getFileByUrl(commUtils.getOssFullUrl(req.getUrl()), ".pdf");
        List<SignBean> signatures = signHelper.verifyPdfSignResult(FileUtils.readFileToByteArray(file));
        return BeanUtil.mapList(mapper, signatures, SignBeanDTO.class);
    }

    /**
     * 时间戳服务加戳
     *
     * @param req 时间戳加戳请求信息
     * @return 加戳后数据包
     * @throws Exception
     */
    public String requestTimestamp(TimestampReq req) throws Exception {
        ServiceRecord record = new ServiceRecord();
        record.setRequestMsg(req.getJson());
        record.setSceneDesc(req.getSceneDesc());
        record.setContractId(req.getContractId());
        byte[] response = new byte[0];
        try {
            response = tsaSession.requestTimeStamp(Mechanism.SHA256, req.getJson().getBytes());
        } catch (Exception e) {
            record.setServiceStatus(false);
            serviceRecordModel.insertServiceRecord(record, SignServerEnum.TIMESTAMP, req);
            throw new Exception(e);
        }
        String responseStr = new String(response);
        record.setResponseMsg(responseStr);
        record.setServiceStatus(true);
        serviceRecordModel.insertServiceRecord(record, SignServerEnum.TIMESTAMP, req);
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "时间戳加签：response = {}", responseStr);
        return responseStr;
    }

    /**
     * 时间戳验签
     *
     * @param req 时间戳验签请求信息
     * @throws PKIException
     */
    public Date verifyTimestamp(VerifyTimestampReq req) throws PKIException {
        boolean result = tsaSession.P7VerifyTimeStamp(req.getBytes().getBytes());
        TimeStamp timeStamp = tsaSession.parseP7TimeStamp(req.getBytes().getBytes());
        if (!result) {
            throw new BusinessException("时间戳验签失败", "ECCT0400");
        }
        return timeStamp.getTasTime();
    }

    /**
     * PDF 摘要签署（短信验证）
     *
     * @param req PDF 文件签署请求类
     * @return 文件签署结果
     * @throws IOException
     */
    public SignedResultDTO userSignPdf(SignPdfReq req) throws IOException {
        SignType signType = EnumUtils.getEnum(SignType.class, req.getSignType());
        Optional.ofNullable(signType).orElseThrow(() -> new BusinessException("签署类型参数错误", "ECCT0303"));
        Contract contract = null;
        // 如果合约 id 不为空，则校验是否正常有效
        if (req.getContractId() != null) {
            contract = contractManagementModel.validContract(req.getContractId(), req);
        }
        SignPos signPos = req.getSignPos();
        PosBean posBean = mapper.map(signPos, PosBean.class);
        SignPDFStreamBean stream = new SignPDFStreamBean();
        // 从 oss 地址上读取要签署的 pdf 文件信息
        File pdfFile = TemplateUtil.getFileByUrl(commUtils.getOssFullUrl(req.getPdfUrl()), ".pdf");
        String fileName = req.getPdfUrl().substring(req.getPdfUrl().lastIndexOf("/") + 1);
        stream.setStream(FileUtils.readFileToByteArray(pdfFile));
        // 保存签署记录
        ServiceRecord record = new ServiceRecord();
        record.setRequestMsg(JSON.toJSONString(req));
        record.setSceneDesc(req.getSceneDesc());
        record.setContractId(req.getContractId());
        // 查询印模数据
        UserSeal userSeal = sealModel.getSealById(req.getSealId(), req);
        String imageBase64 = userSeal.getSealData();
        FileDigestSignResult digestSignResult = null;
        String signServiceId = null;
        try {
            digestSignResult =
                signHelper.doUserSignPdf(req.getAccountId(), imageBase64, stream, posBean, signType, req.getCode());
            signServiceId = digestSignResult.getSignServiceId();
        } catch (BusinessException e) {
            record.setServiceStatus(false);
            serviceRecordModel.insertServiceRecord(record, SignServerEnum.AGREEMENT_SIGN_E, req);
            throw e;
        }
        byte[] signedFile = digestSignResult.getStream();
        // 将签完章的文件更新到原地址
        String fileUrl =
            fileService.uploadFile("oss", new ByteArrayInputStream(signedFile), fileName, commUtils.getOssBucketName());
        // String ossFullUrl = commUtils.getOssFullUrl(fileUrl);
        SignedResultDTO result = new SignedResultDTO();
        result.setSignServiceId(signServiceId);
        result.setSignDetailUrl(digestSignResult.getSignDetailUrl());
        result.setFileUrl(fileUrl);

        record.setResponseMsg(JSON.toJSONString(digestSignResult));
        record.setSignServiceId(signServiceId);
        record.setServiceStatus(true);
        serviceRecordModel.insertServiceRecord(record, SignServerEnum.AGREEMENT_SIGN_E, req);
        // 将签署后的文件地址更新至数据库表中
        updateContractOssPath(contract, fileUrl, req);
        return result;
    }

    /**
     * 平台用户PDF文件批量签署（指定手机号短信验证）
     *
     * @param req 平台用户PDF文件批量签署（指定手机号短信验证）
     * @return 批量签署结果
     */
    public MultiSignedResultDTO batchSignAppointMobilePdfReq(BatchSignPdfAppointMobileReq req) {
        List<SigningParamBean> signInfos = req.getSignInfos();
        // 请求时签署信息，key 为文件 markbit 标识位
        Map<String, SourceSignBean> sourceSigningBean = new HashMap<>(16);
        // 签署对象集合
        List<SignParamBean> signParamBeans = signInfos.stream().map(info -> {
            SignType signType = EnumUtils.getEnum(SignType.class, info.getSignType());
            Optional.ofNullable(signType).orElseThrow(() -> new BusinessException("签署类型参数错误", "ECCT0303"));
            if (StringUtils.isBlank(info.getMarkBit())) {
                throw new BusinessException("客户自由标示位不能为空", "ECCT0001");
            }
            if (sourceSigningBean.get(info.getMarkBit()) != null) {
                throw new BusinessException("客户自由标示位：" + info.getMarkBit() + " 重复", "ECCT0001");
            }
            Contract contract = null;
            // 如果合约 id 不为空，则校验是否正常有效
            if (info.getContractId() != null) {
                contract = contractManagementModel.validContract(info.getContractId(), req);
            }
            sourceSigningBean.put(info.getMarkBit(), new SourceSignBean(info, contract));

            SignPos signPos = info.getSignPos();
            PosBean posBean = mapper.map(signPos, PosBean.class);
            // 从 oss 地址上读取要签署的 pdf 文件信息
            File pdfFile = TemplateUtil.getFileByUrl(commUtils.getOssFullUrl(info.getPdfUrl()), ".pdf");
            String fileName = info.getPdfUrl().substring(info.getPdfUrl().lastIndexOf("/") + 1);
            SignPDFFileBean fileBean = new SignPDFFileBean();
            try {
                fileBean.setBytes(FileUtils.readFileToByteArray(pdfFile));
                fileBean.setFileName(fileName);
                fileBean.setMarkBit(info.getMarkBit());
                fileBean.setOwnerPassword(info.getOwnerPassword());
            } catch (IOException e) {
                IfpLogger.error(Thread.currentThread().getStackTrace()[1].getMethodName(),
                    "平台用户PDF文件批量签署（指定手机号短信验证）， pdf 文件读取异常, case: {}", Throwables.getStackTraceAsString(e));
                throw new BusinessException("服务异常，请联系系统管理员", "ECCT0000");
            }
            // 封装签署对象
            SignParamBean signParamBean = new SignParamBean();
            signParamBean.setSignType(signType);
            signParamBean.setSignPos(posBean);
            signParamBean.setFileBean(fileBean);
            return signParamBean;
        }).collect(Collectors.toList());

        // 查询印模数据
        UserSeal userSeal = sealModel.getSealById(req.getSealId(), req);
        String imageBase64 = userSeal.getSealData();

        // 转调 e 签宝服务
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "转调 e 签宝服务++++++++++++++++++++Start");
        CodeMultiSignResult signResult = signHelper.batchSignWithAppointMobileCode(req.getAccountId(), signParamBeans,
            imageBase64, req.getMobile(), req.getCode());
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "转调 e 签宝服务++++++++++++++++++++End");

        String reqJson = JSON.toJSONString(req);
        return getMultiSignedResult(signResult, reqJson, req.getSceneDesc(), sourceSigningBean, req);
    }

    /**
     * 用户PDF文件批量签署（短信验证）
     *
     * @param req PDF 文件批量签署（短信验证）
     * @return 批量签署结果
     */
    public MultiSignedResultDTO batchSignPdfByCode(BatchSignPdfReq req) {
        List<SigningParamBean> signInfos = req.getSignInfos();
        // 请求时签署信息，key 为文件 markbit 标识位
        Map<String, SourceSignBean> sourceSigningBean = new HashMap<>(16);
        // 签署对象集合
        List<SignParamBean> signParamBeans = signInfos.stream().map(info -> {
            SignType signType = EnumUtils.getEnum(SignType.class, info.getSignType());
            Optional.ofNullable(signType).orElseThrow(() -> new BusinessException("签署类型参数错误", "ECCT0303"));
            if (StringUtils.isBlank(info.getMarkBit())) {
                throw new BusinessException("客户自由标示位不能为空", "ECCT0001");
            }
            if (sourceSigningBean.get(info.getMarkBit()) != null) {
                throw new BusinessException("客户自由标示位：" + info.getMarkBit() + " 重复", "ECCT0001");
            }
            Contract contract = null;
            // 如果合约 id 不为空，则校验是否正常有效
            if (info.getContractId() != null) {
                contract = contractManagementModel.validContract(info.getContractId(), req);
            }
            sourceSigningBean.put(info.getMarkBit(), new SourceSignBean(info, contract));

            SignPos signPos = info.getSignPos();
            PosBean posBean = mapper.map(signPos, PosBean.class);
            // 从 oss 地址上读取要签署的 pdf 文件信息
            File pdfFile = TemplateUtil.getFileByUrl(commUtils.transferFileUrl(info.getPdfUrl()), ".pdf");
            String fileName = info.getPdfUrl().substring(info.getPdfUrl().lastIndexOf("/") + 1);
            SignPDFFileBean fileBean = new SignPDFFileBean();
            try {
                fileBean.setBytes(FileUtils.readFileToByteArray(pdfFile));
                fileBean.setFileName(fileName);
                fileBean.setMarkBit(info.getMarkBit());
                fileBean.setOwnerPassword(info.getOwnerPassword());
            } catch (IOException e) {
                IfpLogger.error(Thread.currentThread().getStackTrace()[1].getMethodName(),
                    "短信批量签署， pdf 文件读取异常, case: {}", Throwables.getStackTraceAsString(e));
                throw new BusinessException("服务异常，请联系系统管理员", "ECCT0000");
            }
            // 封装签署对象
            SignParamBean signParamBean = new SignParamBean();
            signParamBean.setSignType(signType);
            signParamBean.setSignPos(posBean);
            signParamBean.setFileBean(fileBean);
            return signParamBean;
        }).collect(Collectors.toList());

        // 查询印模数据
        UserSeal userSeal = sealModel.getSealById(req.getSealId(), req);
        String imageBase64 = userSeal.getSealData();

        // 转调 e 签宝服务
        CodeMultiSignResult signResult =
            signHelper.batchSignWithCode(req.getAccountId(), signParamBeans, imageBase64, req.getCode());

        String reqJson = JSON.toJSONString(req);
        return getMultiSignedResult(signResult, reqJson, req.getSceneDesc(), sourceSigningBean, req);
    }

    /**
     * e签宝-平台用户无认证PDF文件签署
     *
     * @param req e签宝-平台用户无认证PDF文件签署请求参数
     * @return 批量签署结果
     */
    @Transactional(rollbackFor = Exception.class)
    public SignWithoutAuthResultDTO signWithoutAuth(SignWithoutAuthReq req) throws IOException {
        // Step1：初步整理参数
        SigningWithoutAuthParamBean signingParamBean = req.getSignInfos();
        File pdfFile = TemplateUtil.getFileByUrl(commUtils.getOssFullUrl(signingParamBean.getPdfUrl()), ".pdf");
        String fileName = signingParamBean.getPdfUrl().substring(signingParamBean.getPdfUrl().lastIndexOf("/") + 1);
        SignType signType = EnumUtils.getEnum(SignType.class, signingParamBean.getSignType());
        // Step2: 准备e签宝请求参数
        // Step2.1: 签署文档信息
        SignPDFFileBean file = new SignPDFFileBean();
        try {
            file.setBytes(FileUtils.readFileToByteArray(pdfFile));
            file.setFileName(fileName);
            file.setOwnerPassword(signingParamBean.getOwnerPassword());
        } catch (IOException e) {
            IfpLogger.error(Thread.currentThread().getStackTrace()[1].getMethodName(),
                "e签宝-平台用户无认证PDF文件签署， pdf 文件读取异常, case: {}", Throwables.getStackTraceAsString(e));
            throw new BusinessException("服务异常，请联系系统管理员", "ECCT0000");
        }
        // Step2.2: 签章位置信息
        SignPos signPos = signingParamBean.getSignPos();
        PosBean posBean = mapper.map(signPos, PosBean.class);
        // Step2.3: 印模数据
        UserSeal userSeal = sealModel.getSealById(req.getSealId(), req);
        String imageBase64 = userSeal.getSealData();
        // Step3: 转调 e 签宝服务
        FileDigestSignResult signResult =
            signHelper.signWithoutAuth(req.getAccountId(), file, imageBase64, posBean, signType);
        // Step4: 业务处理
        return getSignWithoutAuthResult(signResult, req);
    }

    /**
     * 上传文件并同时调用 e 签宝无认证 PDF 文件签署，对文件进行签章
     *
     * @param req 上传文件并同时调用 e 签宝无认证 PDF 文件签署，对文件进行签章
     * @return 批量签署结果
     */
    @Transactional(rollbackFor = Exception.class)
    public UploadAndSignWithoutAuthResultDTO uploadFileAndSignWithoutAuth(UploadFileAndSignWithoutAuthReq req)
        throws IOException {
        // Step1：初步整理参数
        SigningWithoutAuthAndUrlParamBean withoutAuthAndUrlParamBean = req.getSignInfos();
        SignType signType = EnumUtils.getEnum(SignType.class, withoutAuthAndUrlParamBean.getSignType());
        // Step2: 准备e签宝请求参数
        // Step2.1: 签署文档信息
        SignPDFFileBean file = new SignPDFFileBean();
        byte[] bytes = org.apache.commons.codec.binary.Base64.decodeBase64(req.getFileBase64());
        file.setBytes(bytes);
        file.setFileName(req.getName());
        file.setOwnerPassword(withoutAuthAndUrlParamBean.getOwnerPassword());
        // Step2.2: 签章位置信息
        SignPos signPos = withoutAuthAndUrlParamBean.getSignPos();
        PosBean posBean = mapper.map(signPos, PosBean.class);
        // Step2.3: 印模数据
        UserSeal userSeal = sealModel.getSealById(req.getSealId(), req);
        String imageBase64 = userSeal.getSealData();
        // Step3: 转调 e 签宝服务
        FileDigestSignResult signResult =
            signHelper.signWithoutAuth(req.getAccountId(), file, imageBase64, posBean, signType);
        // Step4: 业务处理

        // 落库前将文件流置空，不落库处理 by liulx at 2022-05-12 14:53:10
        req.setFileBase64(null);

        return getSignWithoutAuthResultAndFile(signResult, req);
    }

    /**
     * e签宝-平台用户无认证PDF文件签署，并会返回文件流
     *
     * @param signResult e签宝返回结果
     * @param req 上传文件并进行无认证签章请求信息
     * @return 业务处理结果
     */
    private UploadAndSignWithoutAuthResultDTO getSignWithoutAuthResultAndFile(FileDigestSignResult signResult,
        UploadFileAndSignWithoutAuthReq req) throws IOException {

        // 合约ID
        Integer contractId = req.getContractId();
        String sceneDesc = req.getSceneDesc();

        Contract contract = null;
        if (contractId != null) {
            contract = contractManagementModel.validContract(contractId, req);
        }

        // response实体类
        UploadAndSignWithoutAuthResultDTO signWithoutAuthResultDTO = new UploadAndSignWithoutAuthResultDTO();
        // e签宝返回的错误码，0成功，其它均是失败
        int errCode = signResult.getErrCode();
        // Step2: 成功的话，将URL等信息回写数据库，并组装返回信息；
        if (0 == errCode) {
            // Step1: 整理参数
            // 入参Json
            String reqJson = JSON.toJSONString(req);
            // 创建新的无文件流的对象
            FileDigestSignResult tempResult = new FileDigestSignResult();
            tempResult.setMarkBit(signResult.getMarkBit());
            tempResult.setStream(null);
            tempResult.setSignServiceId(signResult.getSignServiceId());
            tempResult.setFilePath(signResult.getFilePath());
            tempResult.setDstFilePath(signResult.getDstFilePath());
            tempResult.setSignDetailUrl(signResult.getSignDetailUrl());
            tempResult.setErrCode(signResult.getErrCode());
            tempResult.setErrShow(signResult.isErrShow());
            tempResult.setMsg(signResult.getMsg());
            // e签宝响应参数Json
            String responseJson = JSON.toJSONString(tempResult);
            // Step2.1: 保存签署记录
            ServiceRecord record = new ServiceRecord();
            record.setRequestMsg(reqJson);
            record.setSceneDesc(sceneDesc);
            record.setContractId(contractId);
            record.setResponseMsg(responseJson);
            record.setSignServiceId(signResult.getSignServiceId());
            record.setServiceStatus(true);
            serviceRecordModel.insertServiceRecord(record, SignServerEnum.AGREEMENT_SIGN_E, req);
            // Step2.2: 回写合约信息主表
            byte[] signedFile = signResult.getStream();
            String fileName = signResult.getSignServiceId() + ".pdf";
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(),
                "上传文件并无认证已签章 PDF 文件上传, start time: {}", System.currentTimeMillis());
            String uploadFileUrl;
            try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(signedFile)) {
                uploadFileUrl =
                    fileService.uploadFile("oss", byteArrayInputStream, fileName, commUtils.getOssBucketName());
            }
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(),
                "上传文件并无认证已签章 PDF 文件上传, end time: {}", System.currentTimeMillis());
            // String fullUrl = commUtils.getOssFullUrl(uploadFileUrl);

            if (contract != null) {
                contract.setGmtModified(LocalDateTime.now());
                contract.setModifiedBy(req.getOperationUserId());
                contract.setPdfOssUrl(uploadFileUrl);
                contractManagementModel.updateById(contract);
            }
            // Step2.3: 组装返回信息
            signWithoutAuthResultDTO.setFileBase64(Base64.toBase64String(signedFile));
            signWithoutAuthResultDTO.setSignServiceId(signResult.getSignServiceId());
            signWithoutAuthResultDTO.setSignDetailUrl(signResult.getSignDetailUrl());
            signWithoutAuthResultDTO.setFileUrl(uploadFileUrl);
            signWithoutAuthResultDTO.setEsignErrCode(signResult.getErrCode());
            signWithoutAuthResultDTO.setEsignMsg(signResult.getMsg());
        }
        // Step3: 失败的话，返回错误码
        if (0 != errCode) {
            signWithoutAuthResultDTO.setEsignErrCode(signResult.getErrCode());
            signWithoutAuthResultDTO.setEsignMsg(signResult.getMsg());
        }
        return signWithoutAuthResultDTO;
    }

    /**
     * e签宝-平台用户无认证PDF文件签署，业务处理
     * 
     * @param signResult e签宝返回结果
     * @param req 平台用户无认证PDF文件签署请求信息
     * @return 业务处理结果
     */
    private SignWithoutAuthResultDTO getSignWithoutAuthResult(FileDigestSignResult signResult, SignWithoutAuthReq req)
        throws IOException {
        // 合约ID
        Integer contractId = req.getContractId();
        String sceneDesc = req.getSceneDesc();

        Contract contract = null;
        if (contractId != null) {
            contract = contractManagementModel.validContract(contractId, req);
        }

        // response实体类
        SignWithoutAuthResultDTO signWithoutAuthResultDTO = new SignWithoutAuthResultDTO();
        // e签宝返回的错误码，0成功，其它均是失败
        int errCode = signResult.getErrCode();
        // Step2: 成功的话，将URL等信息回写数据库，并组装返回信息；
        if (0 == errCode) {
            // Step1: 整理参数
            // 入参Json
            String reqJson = JSON.toJSONString(req);
            // 创建新的无文件流的对象
            FileDigestSignResult tempResult = new FileDigestSignResult();
            tempResult.setMarkBit(signResult.getMarkBit());
            tempResult.setStream(null);
            tempResult.setSignServiceId(signResult.getSignServiceId());
            tempResult.setFilePath(signResult.getFilePath());
            tempResult.setDstFilePath(signResult.getDstFilePath());
            tempResult.setSignDetailUrl(signResult.getSignDetailUrl());
            tempResult.setErrCode(signResult.getErrCode());
            tempResult.setErrShow(signResult.isErrShow());
            tempResult.setMsg(signResult.getMsg());
            // e签宝响应参数Json
            String responseJson = JSON.toJSONString(tempResult);
            // Step2.1: 保存签署记录
            ServiceRecord record = new ServiceRecord();
            record.setRequestMsg(reqJson);
            record.setSceneDesc(sceneDesc);
            record.setContractId(contractId);
            record.setResponseMsg(responseJson);
            record.setSignServiceId(signResult.getSignServiceId());
            record.setServiceStatus(true);
            serviceRecordModel.insertServiceRecord(record, SignServerEnum.AGREEMENT_SIGN_E, req);
            // Step2.2: 回写合约信息主表
            byte[] signedFile = signResult.getStream();
            String fileName = signResult.getSignServiceId() + ".pdf";
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "无认证已签章 PDF 文件上传, start time: {}",
                System.currentTimeMillis());

            String uploadFileUrl;
            try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(signedFile)) {
                uploadFileUrl =
                    fileService.uploadFile("oss", byteArrayInputStream, fileName, commUtils.getOssBucketName());
            }
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "无认证已签章 PDF 文件上传, end time: {}",
                System.currentTimeMillis());
            // String fullUrl = commUtils.getOssFullUrl(uploadFileUrl);

            if (contract != null) {
                contract.setGmtModified(LocalDateTime.now());
                contract.setModifiedBy(req.getOperationUserId());
                contract.setPdfOssUrl(uploadFileUrl);
                contractManagementModel.updateById(contract);
            }
            // Step2.3: 组装返回信息
            signWithoutAuthResultDTO.setSignServiceId(signResult.getSignServiceId());
            signWithoutAuthResultDTO.setSignDetailUrl(signResult.getSignDetailUrl());
            signWithoutAuthResultDTO.setFileUrl(uploadFileUrl);
            signWithoutAuthResultDTO.setEsignErrCode(signResult.getErrCode());
            signWithoutAuthResultDTO.setEsignMsg(signResult.getMsg());
        }
        // Step3: 失败的话，返回错误码
        if (0 != errCode) {
            signWithoutAuthResultDTO.setEsignErrCode(signResult.getErrCode());
            signWithoutAuthResultDTO.setEsignMsg(signResult.getMsg());
        }
        return signWithoutAuthResultDTO;
    }

    /**
     * 批量文件意愿签署（用于人脸确认）
     *
     * @param req PDF 文件意愿签署请求类
     * @return 批量签署结果
     */
    public MultiSignedResultDTO batchWillnessSignPdf(SignPdfWithWillnessReq req) {
        // 将请求时候的markBit作为Key,签署信息作为Value存放到Map;
        // 用于将返回的签署信息，和请求信息一一对应起来;
        Map<String, SourceSignBean> sourceSigningBean = new HashMap<>(16);
        // 转换签署操作人对象
        WillnessInfo willInfo = mapper.map(req.getWillnessInfo(), WillnessInfo.class);
        // 封装签署人信息
        SigningAccount signAccount = new SigningAccount();
        signAccount.setSignAccountId(req.getSignAccountId());

        List<SigningInfoBean> signInfos = req.getSignInfos();
        List<SigningInfo> signInfoList = signInfos.stream().map(sign -> {
            // 校验markBit；
            if (StringUtils.isEmpty(sign.getMarkBit())) {
                throw new BusinessException("客户自由标示位不能为空", "ECCT0001");
            }
            if (sourceSigningBean.get(sign.getMarkBit()) != null) {
                throw new BusinessException("客户自由标示位：" + sign.getMarkBit() + " 重复", "ECCT0001");
            }
            Contract contract = null;
            // 如果合约 id 不为空，则校验是否正常有效
            if (sign.getContractId() != null) {
                contract = contractManagementModel.validContract(sign.getContractId(), req);
            }
            // 校验通过后，放入Map；
            sourceSigningBean.put(sign.getMarkBit(), new SourceSignBean(null, contract));

            // 校验签署类型有效值
            SignType signType = EnumUtils.getEnum(SignType.class, sign.getSignType());
            Optional.ofNullable(signType).orElseThrow(() -> new BusinessException("签署类型参数错误", "ECCT0303"));
            // 封装签署文件对象
            SignPDFStreamBean pdfStreamBean = new SignPDFStreamBean();
            String pdfUrl = commUtils.getOssFullUrl(sign.getPdfUrl());
            // 从 oss 地址上读取要签署的 pdf 文件信息
            File pdfFile = TemplateUtil.getFileByUrl(pdfUrl, ".pdf");
            try {
                pdfStreamBean.setStream(FileUtils.readFileToByteArray(pdfFile));
            } catch (IOException e) {
                IfpLogger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), "意愿签署，转换 pdf 文件异常, case: {}",
                    Throwables.getStackTraceAsString(e));
                throw new BusinessException("服务异常，请联系系统管理员", "ECCT0000");
            }
            // 查询印模数据
            UserSeal userSeal = sealModel.getSealById(sign.getSealId(), req);
            String imageBase64 = userSeal.getSealData();
            SigningInfo signingInfo = new SigningInfo();
            String fileName = pdfUrl.substring(pdfUrl.lastIndexOf("/") + 1);
            pdfStreamBean.setFileName(fileName);
            // e 签宝人脸批量签署，markBit
            pdfStreamBean.setMarkBit(sign.getMarkBit());
            pdfStreamBean.setOwnerPassword(sign.getOwnerPassword());
            signingInfo.setFile(pdfStreamBean);
            signingInfo.setSignPos(BeanUtil.mapList(mapper, sign.getSignPos(), PosBean.class));
            signingInfo.setSignType(signType);
            signingInfo.setSealData(imageBase64);

            return signingInfo;
        }).collect(Collectors.toList());
        // 转调 e 签宝服务
        CodeMultiSignResult signResult = signHelper.batchSignWithWillness(willInfo, signAccount, signInfoList);

        String reqJson = JSON.toJSONString(req);
        return getMultiSignedResult(signResult, reqJson, req.getSceneDesc(), sourceSigningBean, req);

    }

    /**
     * e 签宝批量签署结果统一封装处理
     *
     * @param signResult e 签宝批量签署结果对象
     * @param reqJson 请求时参数 json 字符串
     * @param sceneDesc 业务场景描述
     * @param sourceSignInfos 请求时的文件列表信息
     * @param req 基础请求对象
     * @return 批量签署结果数据传输对象
     */
    private MultiSignedResultDTO getMultiSignedResult(CodeMultiSignResult signResult, String reqJson, String sceneDesc,
        Map<String, SourceSignBean> sourceSignInfos, EnterpriseBaseReqObj req) {
        List<SignedResultDTO> dtoList = new ArrayList<>();
        List<FileDigestSignResult> successList = signResult.getSuccessList();
        List<FileDigestSignResult> failList = signResult.getFailList();
        if (successList == null && failList == null) {
            throw new BusinessException(signResult.getMsg(), String.valueOf(signResult.getErrCode()));
        }

        // 封装签署成功的信息
        if (successList != null && !successList.isEmpty()) {
            successList.forEach(file -> {
                SourceSignBean sourceSignBean = sourceSignInfos.get(file.getMarkBit());
                Contract contract = sourceSignBean.getContract();

                // 保存签署记录
                ServiceRecord record = new ServiceRecord();
                record.setRequestMsg(reqJson);
                record.setSceneDesc(sceneDesc);
                record.setContractId((contract == null ? null : contract.getId()));
                record.setResponseMsg(JSON.toJSONString(file));
                record.setSignServiceId(file.getSignServiceId());
                record.setServiceStatus(true);
                serviceRecordModel.insertServiceRecord(record, SignServerEnum.AGREEMENT_SIGN_E, req);
                byte[] signedFile = file.getStream();
                String fileName = file.getSignServiceId() + ".pdf";
                IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(),
                    "{}文件上传++++++++++++++++++++Start", fileName);
                String uploadFileUrl = fileService.uploadFile("oss", new ByteArrayInputStream(signedFile), fileName,
                    commUtils.getOssBucketName());
                IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(),
                    "{}文件上传++++++++++++++++++++End", fileName);

                // String fullUrl = commUtils.getOssFullUrl(uploadFileUrl);
                SignedResultDTO dto = new SignedResultDTO();
                dto.setSignServiceId(file.getSignServiceId());
                dto.setSignDetailUrl(file.getSignDetailUrl());
                dto.setFileUrl(uploadFileUrl);
                dto.setMarkBit(file.getMarkBit());
                dtoList.add(dto);
                // 更新至数据库
                updateContractOssPath(contract, uploadFileUrl, req);
            });
        }
        // 封装签署失败的信息
        if (failList != null && !failList.isEmpty()) {
            failList.forEach(file -> {
                SourceSignBean sourceSignBean = sourceSignInfos.get(file.getMarkBit());
                Contract contract = sourceSignBean.getContract();
                // 保存签署记录
                ServiceRecord record = new ServiceRecord();
                record.setRequestMsg(reqJson);
                record.setSceneDesc(sceneDesc);
                record.setContractId((contract == null ? null : contract.getId()));
                record.setResponseMsg(JSON.toJSONString(file));
                record.setSignServiceId(file.getSignServiceId());
                record.setServiceStatus(false);
                serviceRecordModel.insertServiceRecord(record, SignServerEnum.AGREEMENT_SIGN_E, req);
            });
        }
        MultiSignedResultDTO multiSignedResultDTO = new MultiSignedResultDTO();
        multiSignedResultDTO.setSuccessList(dtoList);
        multiSignedResultDTO.setFailList(BeanUtil.mapList(mapper, failList, SignResultBean.class));

        return multiSignedResultDTO;
    }

    /**
     * e 签宝批量签署结果统一封装处理
     *
     * @param signResult e 签宝批量签署结果对象
     * @param reqJson 请求时参数 json 字符串
     * @param sceneDesc 业务场景描述
     * @param contractId 合约 id
     * @param req 基础请求对象
     * @return 批量签署结果数据传输对象
     */
    private MultiSignedResultDTO getMultiSignedResultForFace(CodeMultiSignResult signResult, String reqJson,
        String sceneDesc, Integer contractId, EnterpriseBaseReqObj req) {
        List<SignedResultDTO> dtoList = new ArrayList<>();
        List<FileDigestSignResult> successList = signResult.getSuccessList();
        List<FileDigestSignResult> failList = signResult.getFailList();
        if (successList == null && failList == null) {
            throw new BusinessException(signResult.getMsg(), String.valueOf(signResult.getErrCode()));
        }

        // 封装签署成功的信息
        if (successList != null && !successList.isEmpty()) {
            successList.forEach(file -> {

                // 保存签署记录
                ServiceRecord record = new ServiceRecord();
                record.setRequestMsg(reqJson);
                record.setSceneDesc(sceneDesc);
                record.setContractId(contractId);
                record.setResponseMsg(JSON.toJSONString(file));
                record.setServiceStatus(true);
                record.setSignServiceId(file.getSignServiceId());
                serviceRecordModel.insertServiceRecord(record, SignServerEnum.AGREEMENT_SIGN_E, req);
                byte[] signedFile = file.getStream();
                String fileName = file.getSignServiceId() + ".pdf";
                String uploadFileUrl = fileService.uploadFile("oss", new ByteArrayInputStream(signedFile), fileName,
                    commUtils.getOssBucketName());
                String fullUrl = commUtils.getOssFullUrl(uploadFileUrl);
                SignedResultDTO dto = new SignedResultDTO();
                dto.setSignServiceId(file.getSignServiceId());
                dto.setSignDetailUrl(file.getSignDetailUrl());
                dto.setFileUrl(fullUrl);
                dto.setMarkBit(file.getMarkBit());
                dtoList.add(dto);
            });
        }
        // 封装签署失败的信息
        if (failList != null && !failList.isEmpty()) {
            failList.forEach(file -> {
                // 保存签署记录
                ServiceRecord record = new ServiceRecord();
                record.setRequestMsg(reqJson);
                record.setSceneDesc(sceneDesc);
                record.setContractId(contractId);
                record.setResponseMsg(JSON.toJSONString(file));
                record.setSignServiceId(file.getSignServiceId());
                record.setServiceStatus(false);
                serviceRecordModel.insertServiceRecord(record, SignServerEnum.AGREEMENT_SIGN_E, req);
            });
        }
        MultiSignedResultDTO multiSignedResultDTO = new MultiSignedResultDTO();
        multiSignedResultDTO.setSuccessList(dtoList);
        multiSignedResultDTO.setFailList(BeanUtil.mapList(mapper, failList, SignResultBean.class));

        return multiSignedResultDTO;
    }

    /**
     * 获取意愿地址
     *
     * @param req 获取意愿地址请求类
     * @return 意愿任务信息
     */
    public SignAuthBizDTO createSignAuth(CreateSignAuthReq req) {
        Map<String, String> header = apiHelper.getWillnessHeader();
        Map<String, Object> params = new HashMap<>(6);
        params.put("appScheme", req.getAppScheme());
        params.put("idNumber", req.getIdNumber());
        params.put("mobile", req.getMobile());
        params.put("name", req.getName());
        params.put("noticeDeveloperUrl", req.getNoticeDeveloperUrl());
        params.put("redirectUrl", req.getRedirectUrl());

        params.put("from", req.getFrom());
        params.put("isPCRedirect", req.getIsPCRedirect());
        params.put("willTypes", req.getWillTypes());
        params.put("willDefaultType", req.getWillDefaultType());
        params.put("config", req.getConfig());
        JSONObject jsonObject =
            apiHelper.sendApiWillness(header, params, ApiUri.CREATE_SIGN_AUTH, HostConstants.HTTP_METHOD_POST);
        return jsonObject.getObject("data", SignAuthBizDTO.class);
    }

    /**
     * 查询意愿状态
     *
     * @param req 获取意愿地址请求类
     * @return 意愿状态信息
     */
    public WillAuthDTO queryWillAuth(QueryWillAuthReq req) {
        Map<String, String> header = apiHelper.getWillnessHeader();
        Map<String, Object> params = new HashMap<>(1);
        params.put("bizId", req.getBizId());
        JSONObject jsonObject =
            apiHelper.sendApiWillness(header, params, ApiUri.QUERY_WILL_AUTH_RESULT, HostConstants.HTTP_METHOD_POST);
        return jsonObject.getObject("data", WillAuthDTO.class);
    }

    /**
     * cfca 自动化计算 pdf 签署 hash 值
     *
     * @param req 计算 pdf 签章 hash 值请求类
     * @return hash 值信息
     * @throws Exception
     */
    public PdfHashDTO autoCalculatePdfHash(CalculatePdfHashReq req) throws Exception {
        File pdfFile = TemplateUtil.getFileByUrl(commUtils.getOssFullUrl(req.getPdfUrl()), ".pdf");
        byte[] pdf = FileUtils.readFileToByteArray(pdfFile);
        // 查询印模数据
        UserSeal userSeal = sealModel.getSealById(req.getSealId(), req);
        String imageBase64 = userSeal.getSealData();
        // 坐标签章
        SignLocation signLocation = new SignLocation(req.getPage(), req.getPosX(), req.getPosY());
        signLocation.setOffsetX(50);
        signLocation.setOffsetY(50);
        CalculateHashInfo calculateHashInfo =
            new CalculateHashInfo("", imageBase64, signLocation, PrePdfSeal.HashAlgorithm.SHA1);
        String strategyXML = StrategyUtil.createCalculateHashStrategyXML(calculateHashInfo);
        // get pdf hash
        SealClient sealClient =
            new SealClient(cfcaConfig.getSealServerUrl() + SealClient.SLASH + SealClient.PDF_SEAL_SERVLET);
        String getHashValueReturnMessage = sealClient.autoCalculatePdfHash(pdf, null, strategyXML, Constants.KEYTYPE_1);
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "自动化计算 pdf 签署 hash 值: {}",
            getHashValueReturnMessage);
        String code = StringUtil.getNodeText(getHashValueReturnMessage, "Code");
        if ("200".equals(code)) {
            String pdfSealHash = StringUtil.getNodeText(getHashValueReturnMessage, "PdfSealHash");
            String pdfSealSource = StringUtil.getNodeText(getHashValueReturnMessage, "PdfSealSource");
            String pdfHash = StringUtil.getNodeText(getHashValueReturnMessage, "PdfHash");
            String sessionId = StringUtil.getNodeText(getHashValueReturnMessage, "Id");
            PdfHashDTO dto = new PdfHashDTO();
            dto.setPdfSealHash(pdfSealHash);
            dto.setPdfSealSource(pdfSealSource);
            dto.setPdfHash(pdfHash);
            dto.setId(sessionId);
            return dto;
        } else {
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "自动化计算 pdf 签署 hash 值: {}",
                getHashValueReturnMessage);
            throw new BusinessException(StringUtil.getNodeText(getHashValueReturnMessage, "Message"), code);
        }
    }

    /**
     * pdf 文档验签
     *
     * @param req pdf 文档验签请求信息
     * @return pdf 文档所有签名信息列表
     */
    public List<SignedCertDTO> verifyPdfSignByCfca(VerifyPdfSignReq req) throws Exception {
        SealClient sealClient =
            new SealClient(cfcaConfig.getSealServerUrl() + SealClient.SLASH + SealClient.PDF_SEAL_SERVLET);
        File file = TemplateUtil.getFileByUrl(commUtils.getOssFullUrl(req.getUrl()), ".pdf");
        // 0-验章（签），如果扩展域有值验报文内容一致 1-验章（签），证书是否有效" 2-验章（签），证书有效期，证书链 3-验章（签），证书有效期，证书链，CRL"
        String verifyStrategyXML = StrategyUtil.creatVerifyStrategyXML("1");
        String result = sealClient.verifyPdfSeal(FileUtils.readFileToByteArray(file), verifyStrategyXML);

        String code = StringUtil.getNodeText(result, "Code");
        if ("200".equals(code)) {
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "cfca 验章成功: {}", result);
        } else {
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "cfca 验章失败: {}", result);
            throw new BusinessException(StringUtil.getNodeText(result, "Message"), code);
        }
        // 读取证书列表标签
        String nodes = StringUtil.getNodeText(result, "List");
        // 将每个证书标签 Bean，都放入 list 中，后面进行解析封装
        List<String> list = new ArrayList<>();
        StringUtil.getNodesText(nodes, "Bean", list);
        List<SignedCertDTO> certs = new ArrayList<>();
        list.forEach(bean -> {
            String certDN = StringUtil.getNodeText(bean, "CertDN");
            String signatureTime = StringUtil.getNodeText(bean, "SignatureTime");
            String certSn = StringUtil.getNodeText(bean, "CertSn");
            String certNotBefore = StringUtil.getNodeText(bean, "CertNotBefore");
            String certNotAfter = StringUtil.getNodeText(bean, "CertNotAfter");
            String issuer = StringUtil.getNodeText(bean, "Issuer");
            SignedCertDTO certDTO = new SignedCertDTO();
            certDTO.setCertDN(certDN);
            certDTO.setSignatureTime(signatureTime);
            certDTO.setCertSn(certSn);
            certDTO.setCertNotBefore(certNotBefore);
            certDTO.setCertNotAfter(certNotAfter);
            certDTO.setIssuer(issuer);
            certs.add(certDTO);
        });
        return certs;
    }

    /**
     * Pdf合成外部签名
     *
     * @param req Pdf 合成外部签名请求类
     * @throws Exception
     */
    public SignedResultDTO synthesizeOuterSignature(SynthesizeOuterSignatureReq req) throws Exception {
        SealClient sealClient =
            new SealClient(cfcaConfig.getSealServerUrl() + SealClient.SLASH + SealClient.PDF_SEAL_SERVLET);

        String signature = req.getSignature();
        String sessionId = req.getSessionId();
        byte[] result = sealClient.synthesizeOuterSignature(Base64.decode(signature.getBytes(StandardCharsets.UTF_8)),
            sessionId, Constants.KEYTYPE_1);
        Contract contract = null;
        // 如果合约 id 不为空，则校验是否正常有效
        if (req.getContractId() != null) {
            contract = contractManagementModel.validContract(req.getContractId(), req);
        }
        // 保存签署记录
        ServiceRecord record = new ServiceRecord();
        record.setRequestMsg(JSON.toJSONString(req));
        record.setSceneDesc(req.getSceneDesc());
        record.setContractId(req.getContractId());

        if (StringUtil.checkResultDataValid(result)) {
            String pdfUrl = req.getPdfUrl();
            String fileName = pdfUrl.substring(pdfUrl.lastIndexOf("/") + 1);
            File file = File.createTempFile(fileName, ".pdf");
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(result);
            } catch (IOException e) {
                IfpLogger.error(Thread.currentThread().getStackTrace()[1].getMethodName(), "Pdf合成外部签名异常, case: {}",
                    Throwables.getStackTraceAsString(e));
                throw e;
            }
            String fileUrl = fileService.uploadFile("oss",
                new ByteArrayInputStream(FileUtils.readFileToByteArray(file)), fileName, commUtils.getOssBucketName());
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "Pdf合成外部签名成功，合成pdf地址: {}",
                pdfUrl);
            // String ossFullUrl = commUtils.getOssFullUrl(fileUrl);
            record.setServiceStatus(true);
            serviceRecordModel.insertServiceRecord(record, SignServerEnum.AGREEMENT_SIGN_CFCA, req);
            SignedResultDTO dto = new SignedResultDTO();
            dto.setFileUrl(fileUrl);
            updateContractOssPath(contract, fileUrl, req);
            return dto;
        } else {
            String resultMsg = new String(result, StandardCharsets.UTF_8);
            String code = StringUtil.getNodeText(resultMsg, "ErrorCode");
            String msg = StringUtil.getNodeText(resultMsg, "ErrorMessage");
            record.setServiceStatus(false);
            serviceRecordModel.insertServiceRecord(record, SignServerEnum.AGREEMENT_SIGN_CFCA, req);
            throw new BusinessException(msg, code);
        }
    }

    /**
     * 将合约签署后地址更新至合约表中
     *
     * @param contract 合约对象
     * @param ossFullUrl 签署后 OSS 地址
     * @param req 基础请求信息
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateContractOssPath(Contract contract, String ossFullUrl, BaseReqObj req) {
        // 将签署后的文件地址更新至数据库表中
        if (contract != null) {
            contract.setGmtModified(LocalDateTime.now());
            contract.setModifiedBy(req.getOperationUserId());
            contract.setPdfOssUrl(ossFullUrl);
            contractManagementModel.updateById(contract);
        }
    }

}
